/*******************************************************************************
 * Copyright (c) 2008-2009 zhang yuexiang.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ 
package org.xfeep.asura.bootstrap;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.SchemaOutputResolver;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlList;
import javax.xml.bind.annotation.XmlType;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;

import org.xfeep.asura.core.ComponentDefinition;
import org.xfeep.asura.core.ComponentType;
import org.xfeep.asura.core.CoreConsts;
import org.xfeep.asura.core.InjectPropertyItem;
import org.xfeep.asura.core.ReferenceDefinition;
import org.xfeep.asura.core.ScopeType;
import org.xfeep.asura.core.reflect.FieldItem;
import org.xfeep.asura.core.reflect.PropertyItem;
import org.xfeep.asura.core.reflect.TypeItem;

@XmlType(propOrder={"services", "config", "depends", "references", "constProperties", "properties"})
//@XmlAccessorType(XmlAccessType.FIELD)
public class ComponentDefinitionInfo {
	
	@XmlAttribute
	public String name;
	@XmlAttribute(required=true)
	public String implement;
	
	@XmlAttribute
	public AnnotationOverwritePolicyType overwritePolicy = AnnotationOverwritePolicyType.OVERWRITE_ALL;
	
	@XmlAttribute
	public ComponentType type = ComponentType.EAGER;
	@XmlAttribute
	public ScopeType scope = ScopeType.APPLICATION;
	
//	@XmlElements(@XmlElement(name="service", type=String.class))
	@XmlList
	@XmlElement
	public List<String> services = new ArrayList<String>();
	
	@XmlAttribute
	public  String init; //init
	@XmlAttribute
	public  String activate;
	@XmlAttribute
	public  String deactivate;
	@XmlAttribute
	public  String destroy; // destroy
	
	@XmlElement
	public  ConfigReferenceDefinintionInfo config;
	
	@XmlElements(@XmlElement(name="dep", type=DependDefinitionInfo.class))
	public List<DependDefinitionInfo> depends = new ArrayList<DependDefinitionInfo>();
	
	@XmlElements(@XmlElement(name="constProperty", type=PropertyInjectionInfo.class))
	public List<PropertyInjectionInfo> constProperties = new ArrayList<PropertyInjectionInfo>();
	
	@XmlElements(@XmlElement(name="property", type=PropertyInjectionInfo.class))
	public List<PropertyInjectionInfo> properties = new ArrayList<PropertyInjectionInfo>();
	
	@XmlElements(@XmlElement(name="ref", type=ReferenceDefinitionInfo.class))
	public List<ReferenceDefinitionInfo> references = new ArrayList<ReferenceDefinitionInfo>();
	
		
	public ComponentDefinitionInfo() {
	}
	
	public static ComponentDefinitionInfo convert(ComponentDefinition cd){
		return convert(cd, false);
	}
	
	public static ComponentDefinitionInfo convert(ComponentDefinition cd, boolean onlyImplement){
		ComponentDefinitionInfo cdi = new ComponentDefinitionInfo();
		cdi.name = cd.getName();
		cdi.implement = cd.getImplement().getName();
		if (onlyImplement){
			cdi.type = null;
			cdi.scope = null;
			cdi.overwritePolicy = null;
			cdi.services = null;
			if (cdi.name.equals( cdi.implement )){
				cdi.name = null;
			}
			return cdi;
		}
		if (cd.getConstPropertySet() != null){
			for (String[] sp : cd.getConstPropertySet()){
				cdi.constProperties.add(new PropertyInjectionInfo(sp[0], sp[1]));
			}
		}
		cdi.type = cd.getType();
		cdi.scope = cd.getScope();
		for (Class<?> s : cd.getInterfaces()){
			cdi.services.add(s.getName());
		}
		if (cd.getInit() != null){
			cdi.init = cd.getInit().getName();
		}
		if (cd.getActivate() != null){
			cdi.activate = cd.getActivate().getName();
		}
		if (cd.getDeactivate() != null){
			cdi.deactivate = cd.getDeactivate().getName();
		}
		if (cd.getDestroy() != null){
			cdi.destroy = cd.getDestroy().getName();
		}
		StringBuilder ipSb = new StringBuilder();
		for (ReferenceDefinition rd : cd.getReferences()){
			if (CoreConsts.CONFIG_SERVICE_REF_NAME.equals( rd.getName() )){
				if (cdi.config == null || CoreConsts.VAR_SOURCE_CONFIG_ID.equals(cdi.config._cid)){
					cdi.config = ConfigReferenceDefinintionInfo.convert(rd);
				}
			}else if (rd.getBind() == null && rd.getInjectProperties() == null){//is dependency
				cdi.depends.add(DependDefinitionInfo.convert(rd));
			}else{
				cdi.references.add( ReferenceDefinitionInfo.convert(rd) );
				InjectPropertyItem[] injectPropertyItems = rd.getInjectProperties();
				if (injectPropertyItems != null){
					for (InjectPropertyItem ip : injectPropertyItems){
						ipSb.setLength(0);
						cdi.properties.add(new PropertyInjectionInfo(ip.getName(), ipSb.append(rd.getName()).append(".").append(ip.getValueExpression()).toString()));
					}
				}
			}
		}
		return cdi;
	}
	
	@SuppressWarnings("unchecked")
	public ComponentDefinition createComponentDefinitionIngoreAnnotation(ClassLoader loader) throws ClassNotFoundException {
		Class<?> imp = loader.loadClass(this.implement);
		ComponentDefinition cd = new ComponentDefinition();
		cd.setImplement(imp);
		if (name != null && name.length() > 0){
			cd.setName(name);
		}else{
			cd.setName( implement );
		}
		if (services.size() > 0){
			Class<?>[] interfaces = new Class<?>[services.size()];
			for (int i = 0; i < services.size(); i++){
				interfaces[i] = loader.loadClass(services.get(i));
			}
			cd.setInterfaces( interfaces );
		}else {
			ArrayList<Class<?>> itfs = new ArrayList<Class<?>>();
			if (imp.getInterfaces().length > 0){
				for (Class<?> c : imp.getInterfaces()){
					if (c != Serializable.class){
						itfs.add(c);
					}
				}
			}else{
				itfs.add(imp);
			}
			Class<?>[] itfstrs = new Class[itfs.size()];
			cd.setInterfaces( itfs.toArray(itfstrs) );
		}
		cd.setScope ( scope );
		cd.setType( type );
		
		if (!constProperties.isEmpty()){
			String[][] sps = new String[constProperties.size()][2];
			for (int i = 0; i < constProperties.size(); i++){
				PropertyInjectionInfo pi = constProperties.get(i);
				sps[i] = new String[] {pi.name, pi.value};
			}
			cd.setConstPropertySet(sps);
		}
		
		/**
		 * look for config, depends, init, destroy, activate, deactivate method and reference properties or method  from self to parent class recursively
		 * until parent class is null or parent class is a system class
		 */
		
		
		
		List<ReferenceDefinition> references = new ArrayList<ReferenceDefinition>();
		Map<String, TypeItem> itemMap = new HashMap<String, TypeItem>();
		Map<String, Method> methodMap = new HashMap<String, Method>();
		Set<String> allItems = new HashSet<String>();
		Set<String> allMethods = new HashSet<String>();
		for (ReferenceDefinitionInfo rdi : this.references){
			allItems.add(rdi.name);
		}
		for (PropertyInjectionInfo pi : this.properties){
			allItems.add(pi.name);
		}
		if (config != null){
			for (String cp : config.configProperties) {
				allItems.add(cp);
			}
		}
		if (init != null){
			allMethods.add(init);
		}
		
		if (activate != null){
			allMethods.add(activate);
		}
		
		if (deactivate != null){
			allMethods.add(deactivate);
		}
		
		if (destroy != null){
			allMethods.add(destroy);
		}
		
		Class<?> clz = imp;
		
		do{
			for (Method m : clz.getDeclaredMethods()){
				String mn = m.getName();
				if (allMethods.contains(mn)){
					methodMap.put(mn, m);
					allMethods.remove(mn);
				}else if (m.getParameterTypes().length == 1) {
					String itemName = m.getName();
					if (allItems.contains(mn)){
						itemMap.put(itemName, new PropertyItem(clz, itemName, null, m));
						allItems.remove(itemName);
					}else if (itemName.startsWith("set") ){
						itemName = itemName.substring("set".length());
						if (allItems.contains(itemName)){
							itemMap.put(itemName, new PropertyItem(clz, itemName, null, m));
							allItems.remove(itemName);
						}else if (itemName.length() > 1){
							itemName = itemName.substring(0, 1).toLowerCase() + itemName.substring(1);
							if (allItems.contains(itemName)){
								itemMap.put(itemName, new PropertyItem(clz, itemName, null, m));
								allItems.remove(itemName);
							}
						}
					}
					
				}
			}
			for (Field f : clz.getDeclaredFields()){
				String fn = f.getName();
				if (allItems.contains(fn) ){
					itemMap.put(fn, new FieldItem(f));
					allItems.remove(fn);
				}
			}
			clz = clz.getSuperclass();
		}while (  (!allItems.isEmpty() || !allMethods.isEmpty() ) && clz != null && !clz.getName().startsWith("java."));
		
		if (!allItems.isEmpty()){
			throw new IllegalArgumentException("illegal properties " + allItems + " in component definition of  " + implement);
		}
		if (!allMethods.isEmpty()){
			throw new IllegalArgumentException("illegal lifecycle method " + allMethods + " in component definition of  " + implement);
		}
		cd.setInit( methodMap.get(init)  );
		cd.setActivate(methodMap.get(activate));
		cd.setDeactivate(methodMap.get(deactivate));
		cd.setDestroy(methodMap.get(destroy));
		
		//property injection
		Map<String, List<InjectPropertyItem>> injectedPropertiesItemMap = new HashMap<String, List<InjectPropertyItem>>(0);
		if (config != null){
			references.addAll(config.createConfigReferenceDefinintion(cd));
			for (String configProperty : config.configProperties){
				ComponentDefinition.putIntoListValueMap(CoreConsts.CONFIG_SERVICE_REF_NAME, new InjectPropertyItem(configProperty, itemMap.get(configProperty)), (Map)injectedPropertiesItemMap);
			}
		}
		if (!depends.isEmpty()){
			for (DependDefinitionInfo ddi: depends){
				references.add(ddi.createDependReferenceDefinition(cd));
			}
		}
		for (PropertyInjectionInfo pi : properties){
			String propertyValue = pi.value;
			if (propertyValue.length() == 0){
				ComponentDefinition.putIntoListValueMap(CoreConsts.CONFIG_SERVICE_REF_NAME, new InjectPropertyItem(pi.name, itemMap.get(pi.name)), (Map)injectedPropertiesItemMap);
			}else{
				int pos = propertyValue.indexOf('.');
				if (pos < 0){
					throw new IllegalArgumentException("in xml, property  value  {" +propertyValue + "} is illegal in  " + pi.name +"@" + clz.getName());
				}
				String serviceRefName = propertyValue.substring(0, pos);
				ComponentDefinition.putIntoListValueMap(serviceRefName, new InjectPropertyItem(propertyValue.substring(pos+1),itemMap.get(pi.name)), (Map)injectedPropertiesItemMap);
			}
		}
		for (ReferenceDefinitionInfo rdi : this.references){
			references.add(rdi.createReferenceDefinition(cd, itemMap));
		}
		cd.buildOrderedChainAndInjectedItem(references, injectedPropertiesItemMap);
		Object[] indexKeys = new Object[cd.getInterfaces().length+1];
		int i = 0;
		for (Class<?> c : cd.getInterfaces()){
			indexKeys[i++] = c;
		}
		indexKeys[i] = cd.getName();
		cd.setIndexKeys(indexKeys);
		return cd;
	}
	
	public ComponentDefinition createComponentDefinition(ClassLoader loader) throws ClassNotFoundException{
		Class<?> imp = loader.loadClass(this.implement);
		ComponentDefinition annc = ComponentDefinition.create(imp);
		ComponentDefinition xmlc = createComponentDefinitionIngoreAnnotation(loader);
		if (annc == null){
			return xmlc;
		}else{
			if (services.isEmpty() && references.isEmpty()){
				if (name != null && name.length() > 0){
					annc.setName(name);
				}
				return annc;
			}
		}
		return xmlc;
	}
	
	public static void main(String[] args) throws URISyntaxException {
		final File baseDir = new File("./core/org/xfeep/asura/core/bootstrap");
		SchemaOutputResolver sor = new   SchemaOutputResolver() {
		    public Result createOutput( String namespaceUri, String suggestedFileName ) throws IOException {
		        return new StreamResult(new File(baseDir, "asura_component.xsd"));
		    }
		};

		JAXBContext context;
		try {
			context = JAXBContext.newInstance(ComponentDefinitionInfo.class);
			context.generateSchema(sor);
			
		} catch (Throwable e) {
			e.printStackTrace();
		}
		
		
	}
	
}
